package net.andersand.games.yatzy;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import net.andersand.games.yatzy.Dice.Die;

/**
 * The heart of the game
 * 
 * @author Anders Sandberg Nordbø
 */
class Game implements YatzyListener {
	private List<Player> players = new ArrayList<Player>();
	private TurnHandler turnHandler;
	private YatzyFrame frame;
	// change this at runtime to end the game at will:
	private boolean debugOverrideGameOver = false; 

	Game(YatzyFrame frame, List<String> playerNames) {
		this.frame = frame;
		for (String playerName : playerNames) {
			players.add(new Player(playerName));
		}
		turnHandler = new TurnHandler();
	}
	
	public Turn getCurrentTurn() {
		return turnHandler.current();
	}

	/**
	 * Controls progression of turns.
	 */
	private class TurnHandler {
		private int currentPlayerID;

		private TurnHandler() {
			// starting with first player naturally
			currentPlayerID = players.get(0).getId();
		}

		private Turn current() {
			return getPlayer(currentPlayerID).getTurn();
		}

		/**
		 * Returns the Turn of the current player if he has any throws left,
		 * if not passes the turn to the next player.
		 */
		private Turn next() {
			Player player = getPlayer(currentPlayerID);
			if (player.getTurn().complete()) {
				currentPlayerID++; // PlayerId controls turns, so don't mess it up :)
				// if all players have ended their turns
				if (currentPlayerID > players.size()) { // PlayerId starts with 1
					currentPlayerID = players.get(0).getId();
				}
				player = getPlayer(currentPlayerID);
			}
			return player.getTurn();
		}
	}
	
	public Die toggleLockDie(int j) {
		Die d = getCurrentTurn().getPlayer().getDice().getDie(j);
		if (!getCurrentTurn().complete() && !getCurrentTurn().firstRound()) {
			d.toggleLocked();
		}
		return d;
	}

	public void rollDices() {
		Turn turn = turnHandler.next();
		turn.go();
		frame.displayThrow(turn.getPlayer().getDice());
		frame.displayResult("Player: " + turn.getPlayer().toString() + " Got: " + turn.getPlayer().getDice().analyze());
		if (turn.complete()) {
			frame.chooseScore(turn.getPlayer().getDice());
		}
	}

	public Score setScore(ScoreType type) {
		Player player = getCurrentTurn().getPlayer();
		Score score = new Score(type);
		score.setValue(player.getDice().analyze(score.getType()));
		boolean result = player.addScore(score);
		if (result && isGameOver()) {
			doGameOver();
			frame.displayResult("Game over!\nResults:\n" +
					"-----------------------------\n" + doGameOver());
			frame.setGameOver();
		}
		return result ? score : null;
	}
	
	String doGameOver() {
		SortedMap<Integer, Set<Player>> results = getResults();
		StringBuffer sb = new StringBuffer();
		int i=1;
		for (Map.Entry<Integer, Set<Player>> entry : results.entrySet()) {
			sb.append(i++).append(". ");
			// Players may end up with same scores
			Iterator<Player> it = entry.getValue().iterator();
			while (it.hasNext()) {
				Player p = it.next();
				sb.append(p.getName());
				if (it.hasNext()) {
					sb.append(" and ");
				}
			}
			sb.append(" got ").append(entry.getKey()).append(" points\n");
		}
		return sb.toString();
	}

	/**
	 * 
	 * @return results <score, <player...>>
	 */
	SortedMap<Integer, Set<Player>> getResults() {
		// descending sort
		SortedMap<Integer, Set<Player>> sums = new TreeMap<Integer, Set<Player>>(new Comparator<Integer>() {
			public int compare(Integer i1, Integer i2) {
				return i2-i1;
			}
		});
		for (Player p : players) {
			Set<Player> playersWithThatScore = sums.get(p.sum());
			if (playersWithThatScore == null) {
				Set<Player> players = new LinkedHashSet<Player>();
				players.add(p);
				sums.put(p.sum(), players);
			}
			else {
				playersWithThatScore.add(p);
			}
		}
		return sums;
	}

	private boolean isGameOver() {
		if (debugOverrideGameOver) return true;
		// check if every player has all the scores
		for (Player p : players) {
			if (!p.isDone()) {
				return false;
			}
		}
		return true;
	}

	/**
	 * @param id
	 * @return the Player with that id or null
	 */
	public Player getPlayer(int id) {
		for (Player p : players) {
			if (p.getId() == id) {
				return p;
			}
		}
		return null;
	}

	public void resetPlayerTurn() {
		getCurrentTurn().getPlayer().getDice().reset();
	}

	public Player getNextPlayer() {
		int currentID = getCurrentTurn().getPlayer().getId();
		if (currentID >= players.size()) {
			return getPlayer(1);
		}
		return getPlayer(currentID+1);
	}

	public String endGame() {
		frame.setGameOver();
		return doGameOver();
	}

}
